import os
import time
import struct
import select
import signal
import logging
import threading
from abc import ABC, abstractmethod
from .terminal_io import TerminalIO

import paramiko
from paramiko import Channel


class TerminalSshIO(TerminalIO, ABC):

    @classmethod
    def connect_to_ssh(cls, host, username, password, port = 22):
        trans = paramiko.Transport((host, port))
        trans.start_client()
        trans.auth_password(username=username, password=password)
        return trans, trans.open_channel(kind='session')
    def __init__(self, cols: int, rows: int, channel: Channel, logger=None):
        # Initilize.
        #
        # args: cols: columns
        #       rows: rows
        #       cmd: the command to execute.
        #       env: environment variables, if None, set it to os.environ
        self.logger = logger if logger else logging.getLogger()
        self.cols = cols
        self.rows = rows

        self.channel = channel

        self.running = False

        self._read_buf = b""

        self.terminated_callback = lambda: None
        self.stdout_callback = lambda bs: None

    def spawn(self):

        self.logger.info(f"Pty spawned")
        self.channel.get_pty(term='xterm', width=self.cols, height=self.rows)
        self.channel.invoke_shell()
        self.channel.settimeout(0)

        self.running = True

        time.sleep(0.05)

        threading.Thread(name="TerminalIO Read Loop",
                             target=self._read_loop, daemon=True).start()

    def resize(self, rows, cols):
        self.channel.resize_pty(cols, rows)

    def write(self, buffer: bytes):
        self.channel.sendall(buffer)

    def _read_loop(self):
        # read loop to be run in a separated thread
        fd = self.channel
        #poll = select.poll()
        #poll.register(fd, select.POLLIN | select.POLLHUP | select.POLLERR)

        try:
            while self.running:
                readlist, writelist, errlist = select.select([fd], [], [])
                if fd in readlist:
                    buf = fd.recv(1024)
                # 1032 % 4 == 1032 % 3 == 0, avoid truncating utf-8 char

                if len(buf) == 0:
                    break

                self.stdout_callback(buf)
        except OSError:
            pass
        finally:
            self.logger.info("Spawned process has been killed")
            if self.running:
                self.running = False
                self.channel.close()
                self.terminated_callback()


    def terminate(self):
        if self.running:
            os.kill(self.pid, signal.SIGTERM)

            def _check_killed():
                time.sleep(3000)
                if self.is_alive():
                    os.kill(self.pid, signal.SIGKILL)

            threading.Thread(target=_check_killed, daemon=True).start()
            self.running = False

    def is_alive(self):
        try:
            os.kill(self.pif, 0)
            return True
        except OSError:
            self.running = False
            return False